Module('Phosphor.Events', function () {
    function Observable () {}

    this.Observable = Observable;

    Observable.prototype = Object.create(this.EventEmitter.prototype);

    Observable.prototype._value = null;

    Observable.prototype.getValue = function () {
        return this._value;
    };

    Observable.prototype.setValue = function (value) {
        if (this._value !== value) {
            this._value = value;
            this.emit('update', this._value);
        }
    };

    Observable.getValue = function (obj) {
        return obj instanceof Observable ? obj.getValue() : obj;
    };

    function Observer (parents) {
        if (arguments.length > 0) {
            this.setParents(parents);
        }
    }

    this.Observer = Observer;

    Observer.prototype = Object.create(Observable.prototype);

    Observer.prototype.setParents = function (parents) {
        this._parents = parents;
        if (this._parents.length > 0) {
            this.watchAll();
            this.update();
        }
    };

    Observer.prototype.watchAll = function () {
        this._parents.forEach(this.subscribe.bind(this));
    };

    Observer.prototype.watch = function (observable) {
        this._update = this._update || this.update.bind(this);
        observable.on('update', this._update);
    };

    Observer.prototype.unWatch = function (observable) {
        var idx = this._parents.indexOf(observable);

        if (idx != -1 && this._update) { 
            observable.removeSubscriber('update', this._update);
            this._parents.splice(idx, 1);
        }
    };

    Observer.prototype.unWatchAll = function () {
        this._parents.forEach(this.unWatch.bind(this));
    };

    Observer.prototype.cleanup = function () {
        this.unWatchAll();
    };

    Observer.prototype.update = function () {
        var args = this._parents.map(Observable.getValue); 

        try {
            this.setValue(this.evaluate.apply(this, args));
        } catch (err) {
            this.emit('error', err, this); 
        }
    };

    Observer.prototype.evaluate = function () {
        return [].slice.call(arguments);
    };

    Observer.watch = function (observables, evaluate) {
        var observer = new Observer();
        observer.evaluate = evaluate;
        observer.setParents(observables);

        return observer;
    };
});
